/*-*-C-*-
 * ResEd protocol support
 */

#include "resed.h"
#include "main.h"

#include "swicall.h"
#include "wimp.h"
#include "resformat.h"
#include "newmsgs.h"
#include "registry.h"
#include "saveas.h"
#include "toolbox.h"

#include "class.h"
#include "document.h"
#include "genmsgs.h"
#include "protocol.h"


/*
 * This is called whenever we detect that a CSE has died unexpectedly.
 * Tidy up all objects that were being edited by the CSE.
 */

static error * cse_died (CSEPtr cse)
{
    int i = 0;
    RegistryType type;
    void *closure;

    if (cse == NULL)
        return NULL;

    while ((i = registry_enumerate_windows (i, &type, NULL, &closure)) != 0)
        if (type == Document)
        {
            DocumentPtr doc = (DocumentPtr) closure;
            ResourcePtr res = doc->resources;
            while (res)
            {
                if (res->class->cse == cse)
                    res->editing = res->modified = res->importing = FALSE;
                res = res->next;
            }
        }

    cse->taskid = 0;
    return NULL;
}
   

/* OUTGOING MESSAGES */


/*
 * Ask a CSE to load an object or raise it to the top of the window
 * stack.
 */

error * protocol_send_resed_object_load (DocumentPtr doc, ResourcePtr res, Bool force)
{
    MessageResEdObjectLoadRec load;

    load.header.size = offsetof(MessageResEdObjectLoadRec, name) + MAX_OBJECT_NAME;
    load.header.yourref = 0;
    load.header.messageid = MESSAGE_RESED_OBJECT_LOAD;
    load.flags = force ? BIT(0) : 0;
    load.documentID = (Opaque) doc;
    load.objectID = (Opaque) res;
    load.class = res->class->class;
    load.version = res->object->hdr.version;
    load.address = (unsigned int) res->object;
    load.size = toolbox_object_size (res->object);
    memcpy ((void *) load.name, (void *) res->object->hdr.name, MAX_OBJECT_NAME);

    return swi (Wimp_SendMessage,
                R0, EV_USER_MESSAGE_RECORDED,
                R1, &load,
                R2, res->class->cse->taskid,  END);
}


/*
 * The function informs the CSE that a object's name has changed.
 */

error * protocol_send_resed_object_renamed (DocumentPtr doc, ResourcePtr res)
{
    MessageResEdObjectRenamedRec rename;

    if (res->editing == FALSE || res->class->cse->taskid == 0)
        return NULL;

    rename.header.size = offsetof(MessageResEdObjectRenamedRec, name) + MAX_OBJECT_NAME;
    rename.header.yourref = 0;
    rename.header.messageid = MESSAGE_RESED_OBJECT_RENAMED;
    rename.flags = 0;
    rename.documentID = (Opaque) doc;
    rename.objectID = (Opaque) res;
    memcpy ((void *) rename.name, (void *) res->object->hdr.name, MAX_OBJECT_NAME);

    return swi (Wimp_SendMessage,
                R0, EV_USER_MESSAGE,
                R1, &rename,
                R2, res->class->cse->taskid,  END);
}


/*
 * Informs all the CSEs that new sprites have been loaded.
 */

error * protocol_send_resed_sprites_changed ()
{
    MessageResEdSpritesChangedRec mess;

    mess.header.size = sizeof(mess);
    mess.header.yourref = 0;
    mess.header.messageid = MESSAGE_RESED_SPRITES_CHANGED;
    mess.flags = 0;
    return swi (Wimp_SendMessage,
                R0, EV_USER_MESSAGE,
                R1, &mess,
                R2, 0,  END);
}


/* INCOMING MESSAGES */


/*
 * This function is called when the CSE informs us that an
 * object has been changed.  Mark the object as modified
 * so that it's new data will be recovered when a save
 * is attempted.
 */

static error * received_resed_object_modified (MessageResEdObjectModifiedPtr mod)
{
    DocumentPtr doc = (DocumentPtr) mod->documentID;
    ResourcePtr res = (ResourcePtr) mod->objectID;

    if (!document_exists(doc) || res->owner != doc)
        return NULL;                    /* quietly ignore */

    res->modified = TRUE;
    return document_modified (doc, TRUE);
}


/*
 * CSE informs us that it has closed an object.  All we need to do
 * is note that the object is no longer being edited.  Any recovery
 * of data has already happened when the CSE sent us RESED_OBJECT_LOAD.
 */

static error * received_resed_object_closed (MessageResEdObjectClosedPtr closed)
{
    DocumentPtr doc = (DocumentPtr) closed->documentID;
    ResourcePtr res = (ResourcePtr) closed->objectID;

    if (!document_exists(doc) || res->owner != doc)
        return NULL;                    /* quietly ignore */

    res->editing = res->modified = FALSE; /* no longer editing */

    /* Claim caret and input focus into the owning document */
    return document_claim_focus (doc);
}


/*
 * CSE asks us to recover an object's data.  Retrieve it and
 * mark the object as unmodified.  Then reply to the message.
 * If we were unable to recover the object, then inform the CSE.
 * and return an error message.
 */

static error * received_resed_object_sending (MessageResEdObjectSendingPtr sending)
{
    DocumentPtr doc = (DocumentPtr) sending->documentID;
    ResourcePtr res = (ResourcePtr) sending->objectID;
    error *err = NULL;

    if (document_exists(doc) && res->owner == doc)
    {
        MessageResEdObjectLoadedRec loaded;

        if (sending->flags & BIT(0))
        {
            if (res->importing)
                error_box (genmsgs_import_protocol_complete (res, FALSE));

            if (sending->errcode == OBJECT_SENDING_ERROR_FATAL)
                ER ( cse_died (class_lookup_cse (sending->header.taskhandle)));
            return error_lookup ("OpAbort");
        }

        err = document_recover_object_data (doc, res, sending->address, sending->size);

        /* note document modified */
        if (err == NULL)
            document_modified (doc, TRUE);

        loaded.header.size = sizeof(MessageResEdObjectLoadedRec);
        loaded.header.yourref = sending->header.myref;
        loaded.header.messageid = MESSAGE_RESED_OBJECT_LOADED;
        if (err)
        {
            loaded.flags = BIT(0);
            loaded.errcode = OBJECT_SENDING_ERROR_NOMEM; /* assume */
        }
        else
            loaded.flags = 0;
        loaded.documentID = sending->documentID;
        loaded.objectID = sending->objectID;
        (void) swi (Wimp_SendMessage,
                    R0, EV_USER_MESSAGE,
                    R1, &loaded,
                    R2, sending->header.taskhandle,  END);
    }

    if (res->importing)
    {
        if (err)
            error_box (genmsgs_import_protocol_complete (res, FALSE));
        else
        {
            genmsgs_import_protocol_complete (res, TRUE);  /*  at last! */

            /* Claim caret and input focus into the owning document */
            return document_claim_focus (doc);
        }
    }

    return err;
}


/*
 * CSE tells us that it has loaded one of our objects.  We are only
 * really interested in this message if it contains an error indication.
 * The CSE will already have issued an error message to the user if so.
 */

static error * received_resed_object_loaded (MessageResEdObjectLoadedPtr loaded)
{
    DocumentPtr doc = (DocumentPtr) loaded->documentID;
    ResourcePtr res = (ResourcePtr) loaded->objectID;

    if (loaded->flags & BIT(0))
    {
        if (document_exists(doc) && res->owner == doc)
            res->editing = res->modified = FALSE;

        if (res->importing)
            return genmsgs_import_protocol_complete (res, FALSE);

        if (loaded->errcode == OBJECT_LOADED_ERROR_FATAL)
            ER ( cse_died (class_lookup_cse (loaded->header.taskhandle)));
    }
    else
    {
        if (res->importing)
        {
            /*
             * If the force-loaded object was being edited already, then we
             *  simply have to note that the CSE now has a different copy
             *  (ie one in which length fields have been validated) and the
             *  import is complete.
             */

            if (res->editing)
            {
                res->modified = TRUE;
                return genmsgs_import_protocol_complete (res, TRUE);
            }

            /*
             * Otherwise the object must be grabbed back immediately.
             */

            else
            {
                MessageResEdObjectSendRec send;
                error *err;

                send.header.size = sizeof(send);
                send.header.yourref = 0;
                send.header.messageid = MESSAGE_RESED_OBJECT_SEND;
                send.flags = BIT(0);   /* to request deletion after send */
                send.documentID = (Opaque) doc;
                send.objectID = (Opaque) res;
                err = swi (Wimp_SendMessage,
                             R0, EV_USER_MESSAGE_RECORDED,
                             R1, &send,
                             R2, res->class->cse->taskid,  END);

                if (err != NULL)
                {
                    error_box (err);
                    return genmsgs_import_protocol_complete (res, FALSE);
                }
            }
        }
        else
        {
            /* Must not clear the modified flag because we may have
             * merely raised an existing window.
             */
            res->editing = TRUE;
        }
    }
    return NULL;
}


/*
 * This function is called when the shell's attempt to send
 * RESED_OBJECT_LOAD to a CSE bounces.  It marks the CSE as
 * 'not running' and then checks for all objects that were
 * being edited by that CSE.
 */

static error * bounced_resed_object_load (MessageResEdObjectLoadPtr load)
{
    ClassPtr class = class_lookup (load->class);
    if (class == NULL)
        return NULL;

    return cse_died (class->cse);
}


/*
 * This function is called if the RESED_OBJECT_SEND message sent by the
 *  shell as part of the "importing" protocol bounces. Pretty unlikely,
 *  since we've only just received a LOADED message from the addressee.
 */

static error * bounced_resed_object_send (MessageResEdObjectSendPtr send)
{
    ResourcePtr res = (ResourcePtr) send->objectID;
    error_box (genmsgs_import_protocol_complete (res, FALSE));

    return cse_died (res->class->cse);
}



/*
 * Protocol message dispatcher.  The main event handling code uses this
 * to handle all ResEd shell<->CSE protocol messages.  Buf points
 * to a 64-word Wimp event buffer.  The event code is in 'event'.
 * Sets *handled to indicate to the caller whether the event has
 * been handled or not.
 */

error * protocol_incoming_message (int event, int *buf, Bool *handled)
{
    MessageHeaderPtr hdr = (MessageHeaderPtr) buf;
    if (handled) *handled = TRUE;
    switch (event)
    {
    case EV_USER_MESSAGE:
    case EV_USER_MESSAGE_RECORDED:
        switch (hdr->messageid)
        {
        case MESSAGE_RESED_OBJECT_MODIFIED:
            return received_resed_object_modified ((MessageResEdObjectModifiedPtr) buf);
        case MESSAGE_RESED_OBJECT_CLOSED:
            return received_resed_object_closed ((MessageResEdObjectClosedPtr) buf);
        case MESSAGE_RESED_OBJECT_SENDING:
            return received_resed_object_sending ((MessageResEdObjectSendingPtr) buf);
        case MESSAGE_RESED_OBJECT_LOADED:
            return received_resed_object_loaded ((MessageResEdObjectLoadedPtr) buf);
        case MESSAGE_TASK_CLOSE_DOWN:
            if (hdr->taskhandle == 0)
                return NULL;
            return cse_died (class_lookup_cse (hdr->taskhandle));
        }
        break;
    case EV_USER_MESSAGE_ACKNOWLEDGE:
        switch (hdr->messageid)
        {
        case MESSAGE_RESED_OBJECT_LOAD:
            return bounced_resed_object_load ((MessageResEdObjectLoadPtr) buf);
        case MESSAGE_RESED_OBJECT_SEND:
            return bounced_resed_object_send ((MessageResEdObjectSendPtr) buf);
        }
        break;
    }
    if (handled) *handled = FALSE;
    return NULL;
}
